//------------------------------------------------------------------------------
// <copyright company="Telligent Systems">
//     Copyright (c) Telligent Systems Corporation.  All rights reserved.
// </copyright> 
//------------------------------------------------------------------------------

using System;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml.Serialization;
using CommunityServer.Components;

namespace CommunityServer.Configuration
{
	/// <summary>
	/// Summary description for FileSettings.
	/// </summary>
	[Serializable]
	public abstract class AttachmentSettings
	{
		#region Mappings
		private static StringDictionary _mimeMap;

		static AttachmentSettings()
		{
			_mimeMap = new StringDictionary();

			_mimeMap.Add("csv", "application/vnd.ms-excel");
			_mimeMap.Add("css", "text/css");
			_mimeMap.Add("js", "text/javascript");
			_mimeMap.Add("doc", "application/msword");
			_mimeMap.Add("gif", "image/gif");
			_mimeMap.Add("bmp", "image/bmp");
			_mimeMap.Add("htm", "text/html");
			_mimeMap.Add("html", "text/html");
			_mimeMap.Add("jpeg", "image/jpeg");
			_mimeMap.Add("jpg", "image/jpeg");
			_mimeMap.Add("pdf", "application/pdf");
			_mimeMap.Add("png", "image/png");
			_mimeMap.Add("ppt", "application/vnd.ms-powerpoint");
			_mimeMap.Add("rtf", "application/msword");
			_mimeMap.Add("txt", "text/plain");
			_mimeMap.Add("xls", "application/vnd.ms-excel");
			_mimeMap.Add("xml", "text/xml");
			_mimeMap.Add("wmv", "video/x-ms-wmv");
			_mimeMap.Add("wma", "video/x-ms-wmv");
			_mimeMap.Add("mpeg", "video/mpeg");
			_mimeMap.Add("mpg", "video/mpeg");
			_mimeMap.Add("mpa", "video/mpeg");
			_mimeMap.Add("mpe", "video/mpeg");
			_mimeMap.Add("mov", "video/quicktime");
			_mimeMap.Add("qt", "video/quicktime");
			_mimeMap.Add("avi", "video/x-msvideo");
			_mimeMap.Add("asf", "video/x-ms-asf");
			_mimeMap.Add("asr", "video/x-ms-asf");
			_mimeMap.Add("asx", "video/x-ms-asf");
			_mimeMap.Add("swf", "application/x-shockwave-flash");
		}


		public static string GetMapping(string filename)
		{
			string result = null;
			int idx = filename.LastIndexOf('.');

			if (idx > 0 && idx > filename.LastIndexOf('\\'))
				result = _mimeMap[filename.Substring(idx + 1).ToLower(CultureInfo.InvariantCulture)];

			if (result == null)
				return "application/octet-stream";
			else
				return result;
		}
		#endregion

		#region Private Members

		private bool enableFileSystemStorage = false;
		private bool enableDataStoreStorage = false;
		private bool enableDirectLinks = false;

		private string fileSystemStorageLocation = null;
		private string extentensions = "zip,gif,jpg,jpeg,png,bmp,txt,xml";
		private string calculatedFileSystemStorageLocation = null;
		private string virtualPath = null;

		#endregion

		#region Public Properties

		protected const string hiddenFileNamePattern = "{0}\\{1}.csattachment";
		protected const string directFileNamePattern = "{0}\\{1}\\{2}";
		protected const string httpDirectFileNamePattern = "{0}/{1}/{2}/{3}";

		/// <summary>
		/// Do we want to store copies of the files on the file system. If this is false,
		/// we will always read the file values from the database.
		/// </summary>
		[ReadFromConfigurationFileOnly]
		[XmlAttribute("enableFileSystemStorage")]
		public virtual bool EnableFileSystemStorage
		{
			get { return enableFileSystemStorage; }
			set { enableFileSystemStorage = value; }
		}

		/// <summary>
		/// Do we want to keep copies of files in the database?
		/// </summary>
		[ReadFromConfigurationFileOnly]
		[XmlAttribute("enableDataStoreStorage")]
		public virtual bool EnableDataStoreStorage
		{
			get { return enableDataStoreStorage; }
			set { enableDataStoreStorage = value; }
		}

		/// <summary>
		/// Enables linking directly to attachments instead of using an .ashx/aspx extentension.
		/// 
		/// This setting is ideal for high performance sites, since it keeps the files out of asp.net
		/// </summary>
		[ReadFromConfigurationFileOnly]
		[XmlAttribute("enableDirectLinks")]
		public virtual bool EnableDirectLinks
		{
			get { return enableDirectLinks; }
			set { enableDirectLinks = value; }
		}

		/// <summary>
		/// Where on the file system should we store files
		/// </summary>
		[ReadFromConfigurationFileOnly]
		[XmlAttribute("fileSystemStorageLocation")]
		public virtual string FileSystemStorageLocation
		{
			get { return fileSystemStorageLocation; }
			set { fileSystemStorageLocation = value; }
		}

		/// <summary>
		/// Comman seperated list of valid file extentions.
		/// </summary>
		[XmlAttribute("extensions")]
		public virtual string Extensions
		{
			get { return extentensions; }
			set { extentensions = value; }
		}

		/// <summary>
		/// When using Direct link in a cluster, you will need to specify a UNC. This value
		/// can not be coverted to a web link. The VirtualPath property enables you to property
		/// set your UNC value for FileSystemStorageLocation and a web accessible property for 
		/// VirtualPath.
		/// </summary>
		[XmlAttribute("virtualPath")]
		public virtual string VirtualPath
		{
			get
			{
				if (virtualPath != null)
					return virtualPath;
				else
					return FileSystemStorageLocation;
			}
			set { virtualPath = value; }
		}

		/// <summary>
		/// In the configuration file, you can specificy "all" which enables users to use any
		/// file extentensions they choose.
		/// </summary>
		public virtual bool ValidateExtensions
		{
			get { return string.Compare("all", Extensions, true) != 0; }
		}

		/// <summary>
		/// Regular Expression pattern which can be used in validator controls
		/// </summary>
		public virtual string ValidationPattern
		{
			get
			{
				return ValidateExtensions ? string.Format(basePattern, Extensions.Replace(',', '|')) : ".+";
			}
		}

		//private static readonly string basePattern = "(?:[^\\/\\*\\?\\\"\\<\\>\\|\\n\\r\\t]+)\\.(?:{0})";
		protected static readonly string basePattern = "^.+\\.(?:{0})$";
		private Regex _validationRegex = null;

		/// <summary>
		/// Regex which can be used to validate if the current file is of the correct type to save.
		/// </summary>
		[XmlIgnore]
		public Regex ValidationRegex
		{
			get
			{
				if (_validationRegex == null)
				{
					_validationRegex = new Regex(ValidationPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
				}
				return _validationRegex;
			}
		}


		#endregion

		public AttachmentSettings() { }

		#region Public Methods

		public virtual void SaveAttachmentToDisk(PostAttachment pa, int sectionID, int postID)
		{
			string fileName = this.EnableDirectLinks ? GetDirectFileName(sectionID, postID, pa.FileName) : GetHiddenFileName(sectionID, postID);
			SaveToDisk(pa, fileName);
		}

		public void HandleHttpRequest(HttpContext cntx, Section section)
		{
			if (EnableFileSystemStorage)
			{
				WriteFile(cntx, section);
			}
			else
			{
				WritePostAttachment(cntx, section);
			}
		}
		
		public PostAttachment GetAttachment()
		{
			if (EnableFileSystemStorage)
				return GetFile();
			else
				return GetPostAttachment();
		}

		public virtual string DirectPath(PostAttachmentMetaData metaData)
		{
			if (!metaData.IsRemote && this.EnableFileSystemStorage)
			{
				// pull the file to the file system if it does not currently exist
				string fileName = this.EnableDirectLinks ? GetDirectFileName(metaData.SectionID, metaData.PostID, metaData.FileName) : GetHiddenFileName(metaData.SectionID, metaData.PostID);
				if (!File.Exists(fileName))
					SaveAttachmentToDisk(PostAttachments.GetPostAttachtment(metaData.PostID, false), metaData.SectionID, metaData.PostID);
			}

			return string.Format(httpDirectFileNamePattern, this.FileSystemStorageLocation, metaData.SectionID, metaData.PostID, Globals.UrlEncode(metaData.FileName).Replace("+", "%20"));
		}

		public abstract void DeleteAttachmentFromDisk(int postID);

		public virtual void DeleteAttachmentFromDisk(Post p)
		{
			if (p.Attachment != null && !p.Attachment.IsRemote && this.EnableFileSystemStorage)
			{
				string fileName = this.EnableDirectLinks ? GetDirectFileName(p.SectionID, p.PostID, p.Attachment.FileName) : GetHiddenFileName(p.SectionID, p.PostID);
				if (File.Exists(fileName))
					File.Delete(fileName);
			}
		}

		public void DeleteSectionFromDisk(int sectionID)
		{
			string path = Path.Combine(this.CalculateFileSystemStorageLocation(), sectionID.ToString());
			if (Directory.Exists(path))
				Directory.Delete(path, true);
		}

		public bool FileSystemStorageLocationExists
		{
			get
			{
				if (!Directory.Exists(CalculateFileSystemStorageLocation()))
				{
					try { Directory.CreateDirectory(CalculateFileSystemStorageLocation()); }
					catch { return false; }
				}

				return true;
			}
		}

		public static string Encode(string unencoded)
		{
			string[] parts = unencoded.Split('!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '[', ']', '{', '}', '<', '>', ',', '?', '\\', '/', '\'', '+', '=', '~', '`', '|');
			return string.Join("", parts);
		}

		//TODO: fix the paths for the new folder structure, we will need to recurse to get acutal size
		public virtual long FolderSize()
		{
			// Calculate the current cache size
			FileInfo[] files = (new DirectoryInfo(CalculateFileSystemStorageLocation())).GetFiles(CSContext.Current.SiteSettings.SettingsID + ".*");
			long count = 0;
			foreach (FileInfo file in files)
				count += file.Length;
			count = count / 1024;
			return count;
		}

		public virtual long FolderSize(int SectionID)
		{
			// Calculate the current cache size
			FileInfo[] files = (new DirectoryInfo(CalculateFileSystemStorageLocation())).GetFiles(CSContext.Current.SiteSettings.SettingsID + "." + SectionID + ".*");
			long count = 0;
			foreach (FileInfo file in files)
				count += file.Length;
			count = count / 1024;
			return count;
		}

		#endregion

		#region Protected Methods

		protected string CalculateFileSystemStorageLocation()
		{
			if (calculatedFileSystemStorageLocation == null && EnableFileSystemStorage)
			{
				// Make sure it isn't a drive reference like "c:\blah" or a UNC name like "\\machine\share"
				if ((fileSystemStorageLocation.IndexOf(":\\") != -1) || (fileSystemStorageLocation.IndexOf("\\\\") != -1))
					calculatedFileSystemStorageLocation = fileSystemStorageLocation;
				else //if(CSContext.Current.IsWebRequest) the CSContext MapPath can already handle non web requests
					calculatedFileSystemStorageLocation = CSContext.Current.MapPath(fileSystemStorageLocation);

				// Add the end directory separator if missing
				if (!calculatedFileSystemStorageLocation.EndsWith(Path.DirectorySeparatorChar.ToString()))
					calculatedFileSystemStorageLocation += Path.DirectorySeparatorChar;
			}

			return calculatedFileSystemStorageLocation;
		}

		protected virtual string GetFileName()
		{
			CSContext context = CSContext.Current;
			PostAttachmentMetaData pa = PostAttachments.GetPostAttachtmentMetaData(context.PostID);
			return GetHiddenFileName(pa.SectionID, pa.PostID);
		}

		protected virtual PostAttachment GetPostAttachment()
		{
			CSContext context = CSContext.Current;
			return PostAttachments.GetPostAttachtment(context.PostID);
		}
		
		protected virtual PostAttachment GetFile()
		{
			string name = GetFileName();
			PostAttachment pa = null;
			if (!File.Exists(name))
			{
				pa = GetPostAttachment();
				if (pa != null)
					SaveToDisk(pa, name);
			}
			else
			{
				PostAttachmentMetaData data = PostAttachments.GetPostAttachtmentMetaData(CSContext.Current.PostID);
				pa = new PostAttachment(data);
				pa.Content = new FileStream(name, FileMode.Open, FileAccess.Read, FileShare.Read);
			}

			return pa;
		}

		protected virtual void WriteFile(HttpContext cntx, Section section)
		{
			string name = GetFileName();
			PostAttachment pa = null;
			if (!File.Exists(name))
			{
				pa = GetPostAttachment();
				if (pa != null)
					SaveToDisk(pa, name);
			}

			if (name != null)
			{
				cntx.Response.Clear();

				PostAttachmentMetaData data = null;
				if (pa != null)
					data = pa;
				else
					data = PostAttachments.GetPostAttachtmentMetaData(CSContext.Current.PostID);

				ValidateAccess(cntx, data, section);
				SetFileDetails(cntx, data);
				cntx.Response.WriteFile(name);
			}
			else
				Globals.Return404(cntx);
		}

		protected virtual void ValidateAccess(HttpContext cntx, PostAttachmentMetaData pmd, Section section)
		{
			if (pmd == null || section == null || pmd.SectionID != section.SectionID)
				Globals.Return404(cntx);
		}

		protected void SaveToDisk(PostAttachment pa, string name)
		{
			if (pa != null && pa.Content != null && !pa.IsRemote)
			{
				FileInfo fi = new FileInfo(name);
				if (!fi.Directory.Exists)
					fi.Directory.Create();

				using (FileStream fs = new FileStream(name, FileMode.Create, FileAccess.Write, FileShare.None))
				{
					byte[] buffer = new byte[256 * 1024];
					int bytes;

					while ((bytes = pa.Content.Read(buffer, 0, 256 * 1024)) > 0)
						fs.Write(buffer, 0, bytes);
					fs.Close();
				}
			}
		}

		protected virtual void WritePostAttachment(HttpContext cntx, Section section)
		{
			PostAttachment pa = GetPostAttachment();
			if (pa != null && pa.Content != null)
			{
				ValidateAccess(cntx, pa, section);
				SetFileDetails(cntx, pa);

				byte[] buffer = new byte[256 * 1024];
				int bytes;

				while ((bytes = pa.Content.Read(buffer, 0, 256 * 1024)) > 0)
					cntx.Response.OutputStream.Write(buffer, 0, bytes);
				cntx.Response.OutputStream.Flush();
			}
			else
				Globals.Return404(cntx);
		}

		protected string GetHiddenFileName(int sectionID, int postID)
		{
			string filepath = string.Format(hiddenFileNamePattern, sectionID, postID);
			return Path.Combine(this.CalculateFileSystemStorageLocation(), filepath);
		}

		protected string GetDirectFileName(int sectionID, int postID, string fileName)
		{
			string filepath = string.Format(directFileNamePattern, sectionID, postID, fileName);
			return Path.Combine(this.CalculateFileSystemStorageLocation(), filepath);
		}

		#endregion

		#region Private Methods

		private void SetFileDetails(HttpContext cntx, PostAttachmentMetaData data)
		{
			cntx.Response.Cache.SetLastModified(data.DateCreated);
			cntx.Response.ContentType = data.ContentType;

			string disposition;
			if (data.ContentType == "application/pdf" || data.ContentType == "application/octet-stream")
				disposition = "attachment";
			else
				disposition = "inline";

			if (cntx.Request.Browser.Browser.IndexOf("Netscape") != -1)
				cntx.Response.AddHeader("Content-disposition", disposition + "; filename*0*=" + cntx.Server.UrlEncode(data.FileName).Replace("+", "%20") + "");
			else
				cntx.Response.AddHeader("Content-disposition", disposition + "; filename=" + cntx.Server.UrlEncode(data.FileName).Replace("+", "%20") + "");
		}

		#endregion
	}
}